/*
jQEditor
Copyright (c) 2009, jQEditor.com All rights reserved.
MIT Licensed.
Author: jQEditor@163.com
Depends: jQuery1.32+
Date: 2009-11-20  -- 2009-12-30
Version: 0.7

*/

/*
  编辑器核心
*/

/*
 最外层是一个如下结构的 匿名函数
 (function($){
   ......
 })( jQuery );
 
 使用匿名函数的好处是：
 里面的变量都是局部变量，以免和其他冲突
*/
(function($){

var userAgent = navigator.userAgent;
$.Chrome=/chrome/i.test(userAgent);
$.IE = $.browser.msie;
$.IE6 = (function(){
	if (/MSIE [7891]\d?/.test(userAgent)) return false;
	if (/MSIE [56]/.test(userAgent)) return true;
	return false;
})();
$.IE7 = (function(){
	if (/MSIE [891]\d?/.test(userAgent)) return false;
	if (/MSIE [7]/.test(userAgent)) return true;
	return false;
})();
$.create = function(tagName, $){
	var el=document.createElement(tagName);
	if(typeof $ == "function"){
		el = $(el);
	}
	return el;
}

$.isShow = function($el){
	return $el.css('display') != 'none';
}

//局部变量E 是 $.editor 的别名
var E = $.editor = function(option){
	E.mod("open_default")(option);
}

E.num=0;

//编辑器框架方法定义，类似接口，后面会对方法逐个填充实体内容
$.extend(E, {
	version:'0.7',
	eid:'', //当前的iframe eid
	tops:[],//编辑器工具栏距网页顶部的距离，键值是 eid
	maxTops:[],//编辑器工具栏上下移动时最大的高度，不能超过编辑区域的最下端，键值是 eid
	btn:[],//当前点击的按钮，键值是 eid
	logs:{init:[]},//记录运行时的一些提示信息
	ranges:[],//键值是 eid
	textareas:[],//和编辑器绑定的textarea，键值是 eid
	iframes:[], //所有的iframe，键值是 eid
	documents:[],
	windows:[],
	options:[], //所有的iframe option，键值是 eid
	plugins:{}, //所有自定义插件对象，键值是插件名
	icons:[], //按钮背景图标坐标信息
	mods:{}, //所有核心和插件之外的功能，如 panel, toolbar, menu, selection, event, data 等等
	langs:[],//语言包
	styleSkin:[], //编辑器的样式
	styleContent:[], //内容的样式
	defaults:{},//编辑器选项的默认配置
	
	//扩展 $.editor 核心，默认仅可实现一次，被实现后都会有记录
	extend:function(name, obj){},
	isExtend:[],//记录被实现过的属性名称
	
	init: function(){}, //加载语言文件
	tip:function(msg, eid){},//提示信息
	log:function(type, msg){},
	//工具栏按钮点击触发，此函数再调用其他具体的命令或插件，所有点击的入口
	action:function(el){},
	exec:function(name){},//执行一个命令
	execCommand: function(cmd, bool, value){},//封装浏览器默认的 execCommand 方法
	insert: function(html){},
	
	//添加插件，相似功能的可以批量添加
	plugin:function(name, obj){},
	mod:function(name, obj){}, //添加模块
	lang:function(name, obj){}, //添加语言包
	icon:function(name, obj){}, //添加一个背景图标的默认命令坐标信息
	open:function(option){} //创建一个编辑区域
});


//===编辑器方法实现===//

/**
  扩展编辑器自身，用来扩展编辑器对象 E 的核心方法
*/
E.extend = function(name, obj){
	var names=name.split(',');
	for(var i=0; i<names.length; i++){
		name = $.trim(names[i]);
		if (E.isExtend[name]){
			E.log('init', '编辑器已经存在名字为'+ name +'对象');
		}else{
			E[name] = obj;
			E.isExtend[name]=1;
		}
	}
	return E;
}

//实现默认配置
E.extend("defaults", {
	base:'.', //资源文件的base路径
	eid:'editor',
	fullPage:0,
	textarea:'#content',
	lang:'zh-cn',
	icon:'mce',
	theme:'default',
	toolbar:'default',
	contentCss:'default',
	position:'',
	width:800,
	height:300,
	selector:'',
	forcePtag:true,
	autoHeight:true
});

/**
  实现 action 方法
  action方法在按钮点击时触发，然后转到E.exec方法
  @el Element对象
  @option object
*/
E.extend("action", function(el, option, action){
	var cmd, $btn=$(el), name=$btn.attr('name'), eid=option.eid, Lang=option.Lang;
	if ($btn.hasClass('disabled')){
		return E.tip(Lang[name]+' 处于禁用状态');
	}
	
	cmd = $btn.attr('name');
	if (!eid){
		return E.tip('按钮缺少 name=eid 属性');
	}
	
	var win = E.windows[eid];
	win.focus();
	E.exec(name, eid, $btn, action);
});

/**
 实现 tip 方法
 一个向用户提示信息的方法
 @msg string 提示的信息
 @eid string
*/
E.extend("tip", function(msg, eid){
	var Panel = E.mod("Panel"), eid=eid||E.eid;
	Panel?Panel.tip(msg, eid) : alert(msg);
	return 0;
});

/**
 实现 log 方法
 记录启动运行的日志信息，以方便调式
 @type string 消息的类型，随意指定，一般比如加载时的type=init
 @msg string
*/
E.extend("log", function(type, msg){
	if(E.logs[type]){
		if(typeof E.logs[type] !='object'){
			E.logs[type]=[];
		}
		
		if(msg){
			E.logs[type].push(msg);
		}else{
			return E.logs[type];
		}
	}else{
		alert('log type '+type+' is not exist');
	}
	return 0;
});

//实现 init 方法
E.extend("init", function(option){
	var style, script, Lang=E.langs[option.lang];
	if (!Lang){
		//开发模式，采用引用文件的方式
		option = option || E.defaults;
		style = '<link href="'+option.base+'/themes/skin_'+option.theme
			+'.css" rel="stylesheet" type="text/css"/>';
		var script = '<script type="text/javascript" src="'+option.base+'/src/lang/'+option.lang+'.js"/>';
		$('head').append(style).append(script);
	}else if(E.styleSkin.length){
		//生产模式，使用style标签，减少文件引用
		style='<style>'+E.styleSkin.join('\n')+'</style>';
		
		style=style.replace(/url\s*\(/ig, 'url('+option.base+'/themes/');
		$('head').append(style);
	}else{
		alert('init error.');
	}
});

/**
 执行一个插件，此方法一般有action调用
 @name string 命令或插件的名字
 @eid string
 @btn $(Element)，使用jQuery包装袋按钮对象
*/
E.extend("exec", function(name, eid, $btn, action){
	var Plugin = E.plugin(name), Toolbar=E.mod("Toolbar"),
		Data=E.mod("Data"), dom=E.documents[eid], History=E.mod("History");
	
	if(Toolbar.isLock()){
		return E.tip("工具栏被锁定，请稍等...",eid);
	}
	
	//记录当前点击的eid和点击的按钮
	E.eid = eid;
	var pre=E.btn[eid] ? E.btn[eid][0]:0;
	E.btn[eid] = [$btn, pre];
	
	if (Plugin){
		//统一由 Toolbar.click() 调用插件的 click 方法
		Toolbar.click($btn, eid, dom, Plugin, action);
		
		Toolbar.updateState(eid, dom);
		
		//同步数据到关联的表单项
		E.sync(eid);
		
		//当点击redo/undo时，不要执行history.add，否则混乱
		if(!/Redo|Undo/i.test(name) && History) History.add(dom, eid);
	}else{
		E.tip("编辑器没有实现 " +name+ " 功能", eid);
	}
});

/**
 加载插件或获取插件，插件不可以重复添加。
 可以一次添加多个插件，所有插件都会继承 base 对象的所有属性和方法。
 base 对象在plugin方法内。
 @plugin string 插件的名字，一般第一个字母大写，如Font
 @obj object 插件对象
*/
E.extend("plugin", function(name, obj){
	if (E.plugins[name]){
		if (!obj){
			return E.plugins[name];
		}
		E.log('init', '插件 '+ name +' 已经添加过了');
	}else{
		if (!obj){
			E.tip('插件 '+ name +' 不存在');
			return 0;
		}
		
		if (!/object|function/.test(typeof obj)){
			return E.log('init', '插件 '+ name +' 必须是一个 object 或 function，才可以添加');
		}
		
		if (typeof name == 'string'){names = name.split(',')}
		if (!names.length){return}
		var cmd;
		for(var i=0; i<names.length; i++){
			cmd = $.trim(names[i]);
			//给插件设置 name 属性
			if (!cmd){continue}
			//E.plugins[cmd] = obj; //这样不安全
			
			//下面这样比较好，所有插件的基础模型，相当于基类
			var Plugin, base = {
				name:cmd,  //插件名称
				cache:[], //缓存数据
				inputs:[], //面板当中表单的值
				$btn:null, //点击的DIV按钮，是一个jQuery对象
				eid:'',
				dom:null,
				
				//在插件的click方法当中调用
				set:function($btn, dom, eid){
					this.$btn = $btn;
					this.dom = dom;
					this.eid = eid;
				}
			}
			Plugin = E.plugins[cmd] = (typeof obj == 'object')?$.extend(base, obj):obj;
			//执行插件的初始化函数
			if(typeof Plugin.init=='function'){
				Plugin.init();
			}
		}
		
	}
});

//实现 mod 方法
E.extend("mod", function(name, obj){
	if (E.mods[name]){
		if (!obj){
			return E.mods[name];
		}
		E.log('init', '模块 '+ name +' 已经添加过了');
	}else{
		if (!obj){
			E.tip('模块 '+ name +' 不存在');
			return 0;
		}
		
		if ( !/object|function/.test(typeof obj)){
			E.tip('模块 '+ name +' 必须是一个 object 或 function，才可以添加');
			return 0;
		}
		//给插件设置 name 属性
		obj['name'] = name;
		E.mods[name] = obj;
	}
});

//实现 icon 方法
E.extend("icon", function(name, obj){
	if (E.icons[name]){
		if(!obj){
			return E.icons[name];
		}
		//使用扩展的方式，方便补充
		E.icons[name]=$.extend(E.icons[name], obj);
	}else{
		if (!obj){
			E.tip('icon '+ name +' 不存在');
			return 0;
		}
		
		E.icons[name] = obj;
	}
});

//实现 lang 方法
E.extend("lang", function(name, obj){
	if (E.langs[name]){
		if(!obj){
			return E.langs[name];
		}
		
		//使用扩展的方式，方便补充
		E.langs[name]=$.extend(E.langs[name], obj);
	}else{
		if (!obj){
			E.tip('lang '+ name +' 不存在');
			return;
		}
		E.langs[name] = obj;
	}
});

/**
 将一段html插入到光标所在的位置
 @mode string 如果是'text'字符串则插入文本，否则插入html
*/
E.extend("insert, pasteHTML", function(html, mode){
	var Data = E.mod("Data");
	if(!Data) return;
	if (mode == 'text') Data.insertText(html);
	else Data.insertHtml(html);
	return 1;
});

/**
 将光标定位到编辑器的光标记忆处
*/
E.extend("focus", function(){
	var win=E.windows[E.eid];
	if ($.IE){
		var range = E.ranges[E.eid];
		if(range && range.select){
			range.select();
		}
	}
	win && win.focus();
});

/**
 在表单项和编辑器之间同步数据
 @flag bool 如果是true 则将数据同步到编辑器
*/
E.extend("sync", function(eid, flag){
	eid = eid || E.eid;
	var Data = E.mod("Data");
	if(Data) Data.sync(eid, flag);
});

//实现 execCommand 方法
E.extend("execCommand", function(cmd, bool, value){
	if (bool === null) bool = false;
	if(!$.IE && cmd=="BackColor"){
		cmd = "hiliteColor";
	}
	E.focus();
	E.dom.execCommand(cmd, bool, value);
	E.sync();
});

/**
 实现非IE浏览器执行bold命令时采用b标签，而不是使用span和css样式实现加粗
 Before any other execCommands. Tested on ff3 ie7 ie8 safari chrome
 URL: http://stackoverflow.com/questions/536132/stylewithcss-for-ie
*/
E.extend("_closeStyleWithCSS", function(dom){
	try {
		dom.execCommand("styleWithCSS", 0, false);
	}catch(e){
		try {
			dom.execCommand("useCSS", 0, true);
		}catch(e){
			try{
				dom.execCommand('styleWithCSS', false, false);
			}catch(e){}
		}
	}
});

/**
 注册系统默认命令
 包含一些不需要额外写代码的功能
 */
var _cmds="Bold,Italic,Underline,StrikeThrough"
	+",JustifyLeft,JustifyRight,JustifyCenter"
	+",Outdent,Indent,InsertOrderedList,InsertUnorderedList"
	+",SubScript,SuperScript,RemoveFormat";
E.plugin(_cmds, {
	type:'base',
	click:function($btn, dom, eid){
		var cmd = this.name, Selection=E.mod("Selection"), range=E.ranges[eid];
		var CMDS = /bold|italic|underline|strikeThrough/i;
		//如果不是ie浏览器则强制使用IE的格式，以保持格式统一
		if (!$.IE && CMDS.test(cmd) && E._closeStyleWithCSS){
			E._closeStyleWithCSS(dom);
		}
		//$btn.toggleClass('checked');
		
		//range.select();必须在 execCommand 的前面，否则不起作用
        if ($.IE){
			var selectedText=Selection.get();
			
			//在IE下，未选中文字时，点击加粗等按钮，会将光标周围的字母变为加粗等状态，如下处理后正常
			if (!selectedText && range && CMDS.test(cmd)){
				var tmp = $("#tmp_cmd", dom);
				if($.trim(tmp.html()) =="") tmp.remove();
				else tmp.removeAttr("id");
				Selection.set('<strong id="tmp_cmd"></strong>');
				var tmp = dom.getElementById("tmp_cmd");
				
	            range.moveToElementText(tmp);
				range.collapse(true);
				range.select();
			}
		}
		
		/*
		else{
			//这里不需要这些代码，有这些代码反而不好，仅需要win.focus()即可，已经在E.action里面执行了，顾这里不再需要
			var win = E.iframes[E.eid].contentWindow;
			var selection=win.getSelection();
			selection.addRange(range);
		}
		*/
		E.execCommand(cmd);
		return 1;
	}
});


/**
 创建一个可编辑的 iframe 区域
 */
E.extend("iframe", function(option, container, content){
	var iframe = $.create('iframe'), html="", dom;
	if(typeof container != 'object'){
		E.tip("放置 iframe 容器不是一个对象");
		return iframe;
	}
	if (/^\w{1,50}$/i.test(content) && $("#"+content).size()>0){
		var input=$("#"+content);
		E.textareas[option.eid] = input;
		content = input.val();
	}else{
		content = content || '<p></p>';
	}
	//注意！必须在对iframe进行操作之前将其添加到页面上去，否则出错
	$(container).empty();
	$(iframe).attr({border:'0',frameborder:'no'}).css({width:'100%', height:'100%'})
		.appendTo(container);//注意：appendTo返回的是$(container)，所以要放在最后。
	
	dom = iframe.contentDocument || iframe.contentWindow.document;
	dom.open();
	html = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" '
		+'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
		+'<html xmlns="http://www.w3.org/1999/xhtml"><head>';
	if(E.styleContent.length){
		html+='<style>'+E.styleContent.join('\n')+'</style>\n';
	}else if (option.contentCss){
		html+='<LINK href="'+option.base+'/themes/content_'+option.contentCss+'.css" type="text/css" rel="stylesheet">';
	}
	
	html+='</head><body spellcheck="false" class="editor-post">'+content+'</body></html>';
	
	dom.write(html);
	dom.close();

	if ($.IE) {
		dom.body.contentEditable = "true";
	}else{
		dom.designMode = "On";
	}
	
	return iframe;
});

//对所有的插件设置图标、语言等配置信息
E.extend('pluginOption', function(option){
	for(var i in E.plugins){
		Plugin=E.plugins[i];
		var la=Plugin['lang'], obj1={}, cmd=Plugin.name;
		if(la){
			obj1[cmd]=la[option.lang];
			E.lang(option.lang, obj1);
		}
		
		var icon = Plugin['icon'], obj2={};
		if(icon){
			obj2[cmd]=icon;
			E.icon(option.icon, obj2);
		}
		
		var styleE = Plugin['styleSkin'];
		if(styleE){
			E.styleSkin = $.extend(E.styleSkin, styleE);
		}
		
		var styleC = Plugin['styleContent'];
		if(styleC){
			E.styleContent = $.extend(E.styleContent, styleC);
		}		
	}
});

//创建编辑器的一种方式
E.mod("open_default", function(option){
	var iframe, html, dom, EL_Editor, EL_Toolbar, EL_Content, eid,Toolbar=E.mod("Toolbar");
	var self = E.mod("open_default");
	for (i in E.defaults){
		if (typeof option[i] == 'undefined'){
			option[i] = E.defaults[i];
		}
	}
	E.eid = eid = option.eid;
	E.options[eid] = option;
	
	//必须等到语言文件加载完成才可以继续执行，否则循环检测等待
	if(E.styleSkin.length){
		E.init(option);
	}else if(!E.langs[option.lang]){
		!option.loading && E.init(option);
		option.loading = 1;
		return setTimeout(function(){
			self(option);
		},100);
	}
	
	option.Lang=E.lang(option.lang);
	
	E.pluginOption(option);
	
	//构建编辑器
	EL_Editor = $.create('div');
	EL_Toolbar = $.create('div');
	Wrap_Toolbar = $.create('div');
	EL_Content = $.create('div');
	
	$(EL_Content).css({width:'100%', height:option.height}).addClass('editor_content');
	$(EL_Toolbar).attr("id", eid+"_toolbar").addClass('editor_toolbar');
	
	//生成工具栏按钮
	html = Toolbar.build(option);
	
	$(EL_Toolbar).append(html).appendTo(Wrap_Toolbar);
	
	
	//create EL_Editor
	$(EL_Editor).attr('id', eid).css({width:option.width}).addClass("editor");
	
	var pos = option['position'], pos_toolbar=option['position_toolbar'],
		pos_content=option['position_content'];
	
	//工具栏和内容区同在一个div区域
	if (pos && $(pos).size() ==1){
		$(EL_Editor).append(Wrap_Toolbar).append(EL_Content);
		$(option.position).empty().append(EL_Editor);
		
	}else if (pos_toolbar && pos_content){
		//工具栏和内容区域分别在不同的div区域
		var div1=$(Wrap_Toolbar), div2=$(pos_content);
		var num1=div1.size(), num2=div2.size();
		if (num1 !=1){
			return E.tip('place option.position_toolbar has '+num1);
		}
		if (num2 !=1){
			return E.tip('place option.position_content has '+num2);
		}
		
		$(EL_Editor).append(EL_Content);
		div1.append(EL_Toolbar);
		div2.empty().append(EL_Editor);
		
	}else{
		return E.tip('place set option.position');
	}
	
	iframe = E.iframe(option, EL_Content, '');
	$(iframe).attr({'name':eid});
	
	dom = iframe.contentDocument || iframe.contentWindow.document;
	E.iframes[eid] = iframe;
	E.windows[eid] = iframe.contentWindow;
	E.documents[eid] = dom;
	E.dom = dom;
	
	//给工具栏按钮设置点击事件
	if (EL_Toolbar){
		var btns = $(EL_Toolbar).children('div');
		
		if($.IE6){ //IE6需要使用程序模仿:hover
			//btns.mouseover().mouseout();
			btns.hover(
				function(){$(this).addClass("checked")},
				function(){$(this).removeClass("checked")}
			);
		}
		
		btns.each(function(){
			var btn=$(this), _this=this, menu=btn.children('.menu'), name=btn.attr('name');
			if(menu.length){
				btn.children('.img').click(function(){
					E.action(_this, option);
					return false;//阻止事件冒泡
				});
				
				menu.click(function(){
					var Plugin=E.plugin(name);
					//Plugin.menu(btn, dom, eid);
					E.action(_this, option, 'menu');
					return false;//阻止事件冒泡
				});
			}else{
				btn.click(function(){
					E.action(_this, option);
					return false;//阻止事件冒泡
				});
			}
		});
		
		/*
		var Panel=E.mod("Panel");
		btns.mouseenter(function(){
			Panel.hide();
			return false;
		});
		*/
	}
	
	
	/*
	下面是和插件、模块相关的代码，需要先确定相应的插件、模块已经加载，才可以调用其方法
	*/
	var Event=E.mod("Event"), AutoHeight=E.plugin("AutoHeight"),
		$bar = $(EL_Toolbar), $wrap=$(Wrap_Toolbar), top=$bar.offset().top;
	
	//由内容的变化触发改变编辑器内容区域的高度，则不需要显示编辑区域的下列边框
	if (option.autoHeight && AutoHeight){
		$(iframe).attr({scrolling:'no'});
	}

	E.tops[eid] = Math.round(top);
	
	//同步数据，将eid关联的表单项当中的数据同步到编辑器当中
	E.sync(eid, true);
	
	//初始化时禁用 Redo, Undo 按钮
	Toolbar.disable("Redo,Undo", true, eid);
	
	//初始化事件
	if(Event) Event.init(dom, eid);
	
	//给工具栏外层的div设置一个高度，保证工具栏浮动后，下面的元素不会向上窜
	setTimeout(function(){
		//if(History) History.add(dom, eid); //不需要在这里进行历史记录，同步数据时已经记录
		$bar.css('width', $bar.width());
		$wrap.height($bar.outerHeight());
	},1000);
});

})(jQuery);
